﻿///<copyright>
/// Copyright © 2009 Stephen Styrchak. All Rights Reserved.
///</copyright>
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace Ferpect.ComponentModel.Sprites
{
    public abstract class SpriteGroup<T> : Sprite, IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable, IViewportService
        where T : Sprite
    {
        private Collection<T> sprites;
        private Vector2[] anchorPositions;
        private Rectangle area;
        private Container container;

        protected SpriteGroup()
        {
            this.sprites = new Collection<T>();
            this.anchorPositions = new Vector2[9];
        }

        protected SpriteGroup(IList<T> list)
        {
            if (list == null) throw new ArgumentNullException("list");
            this.sprites = new Collection<T>(list);
        }

        protected SpriteGroup(Collection<T> innerCollection)
        {
            if (innerCollection == null) throw new ArgumentNullException("innerCollection");
            this.sprites = innerCollection;
        }

        protected virtual void SetInnerCollection(Collection<T> innerCollection)
        {
            if (innerCollection == null) throw new ArgumentNullException("innerCollection");
            this.sprites = innerCollection;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                foreach (T child in this)
                {
                    child.Disposed -= this.OnChildDisposed;
                    child.Dispose();
                }
            }
            this.Clear();
            base.Dispose(disposing);
        }

        private void OnChildDisposed(object sender, EventArgs args)
        {
            T child = sender as T;
            if (child != null && this.Contains(child))
            {
                if (this.container != null)
                {
                    this.container.Remove(child);
                }
                this.Remove(child);
            }
        }

        protected virtual void OnAfterItemAdded(T item)
        {
        }

        protected virtual void OnAfterItemRemoved(T item)
        {
        }

        protected override void OnAfterPositionChanged()
        {
            this.UpdateAnchorPositions();
            base.OnAfterPositionChanged();
        }

        private void UpdateAnchorPositions()
        {
            this.area = new Rectangle();
            Vector2 temp = this.Position - this.Origin;
            this.area.X = (int)temp.X;
            this.area.Y = (int)temp.Y;
            Vector2 size = this.GetSize();
            this.area.Width = (int)size.X;
            this.area.Height = (int)size.Y;

            this.anchorPositions[ToIndex(RelativeLocation.TopLeft)] = ViewportService.ComputeAnchorPosition(RelativeLocation.TopLeft, area);
            this.anchorPositions[ToIndex(RelativeLocation.TopCenter)] = ViewportService.ComputeAnchorPosition(RelativeLocation.TopCenter, area);
            this.anchorPositions[ToIndex(RelativeLocation.TopRight)] = ViewportService.ComputeAnchorPosition(RelativeLocation.TopRight, area);
            this.anchorPositions[ToIndex(RelativeLocation.MiddleLeft)] = ViewportService.ComputeAnchorPosition(RelativeLocation.MiddleLeft, area);
            this.anchorPositions[ToIndex(RelativeLocation.MiddleCenter)] = ViewportService.ComputeAnchorPosition(RelativeLocation.MiddleCenter, area);
            this.anchorPositions[ToIndex(RelativeLocation.MiddleRight)] = ViewportService.ComputeAnchorPosition(RelativeLocation.MiddleRight, area);
            this.anchorPositions[ToIndex(RelativeLocation.BottomLeft)] = ViewportService.ComputeAnchorPosition(RelativeLocation.BottomLeft, area);
            this.anchorPositions[ToIndex(RelativeLocation.BottomCenter)] = ViewportService.ComputeAnchorPosition(RelativeLocation.BottomCenter, area);
            this.anchorPositions[ToIndex(RelativeLocation.BottomRight)] = ViewportService.ComputeAnchorPosition(RelativeLocation.BottomRight, area);

            foreach (Sprite sprite in this.sprites)
            {
                sprite.SetAnchorPosition(this);
            }
        }

        private static int ToIndex(RelativeLocation anchor)
        {
            switch (anchor)
            {
                case RelativeLocation.TopLeft:
                    return 0;
                case RelativeLocation.TopCenter:
                    return 1;
                case RelativeLocation.TopRight:
                    return 2;
                case RelativeLocation.MiddleLeft:
                    return 3;
                case RelativeLocation.MiddleCenter:
                    return 4;
                case RelativeLocation.MiddleRight:
                    return 5;
                case RelativeLocation.BottomLeft:
                    return 6;
                case RelativeLocation.BottomCenter:
                    return 7;
                case RelativeLocation.BottomRight:
                    return 8;
            }
            throw new ArgumentOutOfRangeException("anchor");
        }

        public override void SetAnchorPosition(IViewportService viewportService)
        {
            base.SetAnchorPosition(viewportService);
            this.UpdateAnchorPositions();
        }

        public override void LoadContent(ContentManager contentManager)
        {
            ISite site = this.Site;
            if (site == null)
            {
                throw new InvalidOperationException("SpriteGroup.Site property must be set before calling SpriteGroup.LoadContent.");
            }
            if (this.container == null)
            {
                IGameLoop gameState = (IGameLoop)this.Site.GetService(typeof(IGameLoop));
                this.container = new Container(gameState.Services);
            }
            foreach (Sprite sprite in this)
            {
                if (sprite.Site == null)
                {
                    this.container.Add(sprite);
                }
                sprite.LoadContent(contentManager);
            }
        }

        #region IList<Sprite> Members

        public int IndexOf(T item)
        {
            return this.sprites.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            if (this.container != null)
            {
                this.container.Add(item);
            }
            item.Disposed += this.OnChildDisposed;
            this.sprites.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            T item = this.sprites[index];
            this.sprites.RemoveAt(index);
            item.Disposed -= this.OnChildDisposed;
            if (this.container != null)
            {
                this.container.Remove(item);
            }
        }

        public T this[int index]
        {
            get
            {
                return this.sprites[index];
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                if (index < this.sprites.Count)
                {
                    this.RemoveAt(index);
                }
                this.Insert(index, value);
            }
        }

        #endregion

        #region ICollection<Sprite> Members

        public void Add(T item)
        {
            if (this.container != null)
            {
                this.container.Add(item);
            }
            item.Disposed += this.OnChildDisposed;
            this.sprites.Add(item);
            this.OnAfterItemAdded(item);
        }

        public void Clear()
        {
            while (this.sprites.Count > 0)
            {
                this.RemoveAt(this.sprites.Count - 1);
            }
        }

        public bool Contains(T item)
        {
            return this.sprites.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            for (int i = arrayIndex; i < array.Length; ++i)
            {
                this.Add(array[i]);
            }
        }

        public int Count
        {
            get { return this.sprites.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(T item)
        {
            bool removed = this.sprites.Remove(item);
            if (removed)
            {
                this.OnAfterItemRemoved(item);
                item.Disposed -= this.OnChildDisposed;
                if (this.container != null)
                {
                    this.container.Remove(item);
                }
            }
            return removed;
        }

        #endregion

        #region IEnumerable<Sprite> Members

        public IEnumerator<T> GetEnumerator()
        {
            return this.sprites.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.sprites.GetEnumerator();
        }

        #endregion

        #region IList Members

        public int Add(object value)
        {
            ((IList<T>)this).Add((T)value);
            return this.sprites.Count - 1;
        }

        public bool Contains(object value)
        {
            return this.sprites.Contains((T)value);
        }

        public int IndexOf(object value)
        {
            return this.sprites.IndexOf((T)value);
        }

        public void Insert(int index, object value)
        {
            ((IList<T>)this).Insert(index, (T)value);
        }

        public bool IsFixedSize
        {
            get { return false; }
        }

        public void Remove(object value)
        {
            ((IList<T>)this).Remove((T)value);
        }

        object IList.this[int index]
        {
            get
            {
                return ((IList<T>)this)[index];
            }
            set
            {
                ((IList<T>)this)[index] = (T)value;
            }
        }

        #endregion

        #region ICollection Members

        public void CopyTo(Array array, int index)
        {
            for (int i = index; i < array.Length; ++i)
            {
                this.Add((T)array.GetValue(i));
            }
        }

        public bool IsSynchronized
        {
            get { return false; }
        }

        public object SyncRoot
        {
            get { return ((ICollection)this.sprites).SyncRoot; }
        }

        #endregion

        #region IViewportService Members

        public Rectangle ViewportArea
        {
            get { return this.area; }
        }

        public Rectangle TitleSafeArea
        {
            get { return this.area; }
        }

        public Vector2 GetAnchorPosition(RelativeLocation anchor)
        {
            return this.anchorPositions[ToIndex(anchor)];
        }

        public Vector2 GetAnchorPosition(RelativeLocation anchor, bool titleSafe)
        {
            return this.anchorPositions[ToIndex(anchor)];
        }

        #endregion
    }
}
